import os
import platform
import time

import appium.version
from py.xml import html
import psutil
import pytest
import requests
from appium import webdriver as app_webdriver

from poium.common.logging import logger
from selenium import webdriver
from selenium.webdriver.safari.options import Options as SafariOptions
from selenium.webdriver import Remote
from selenium.webdriver.chrome.service import Service as ChromeService
from selenium.webdriver.firefox.service import Service as FirefoxService
from webdriver_manager.chrome import ChromeDriverManager
from webdriver_manager.firefox import GeckoDriverManager

from config import RunConfig
from database import MySql

# 项目目录配置
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
UI_REPORT_DIR = BASE_DIR + "/test_report/UI/"
API_REPORT_DIR = BASE_DIR + "/test_report/API/"
APP_REPORT_DIR = BASE_DIR + "/test_report/APP/"


# 设置报告的标题
def pytest_html_report_title(report):
    if RunConfig.RUN == None or RunConfig.RUN.lower() == 'ui':
        report.title = "WEB UI自动化测试报告"
    elif RunConfig.RUN.lower() == 'api':
        report.title = "接口自动化测试报告"
    else:
        report.title = 'APP自动化测试报告'


# 设置用例描述表头
def pytest_html_results_table_header(cells):
    cells.insert(2, html.th('用例描述'))
    # cells.insert(2, html.th('Description'))
    if RunConfig.RUN == None or RunConfig.RUN.lower() == 'ui' or RunConfig.RUN.lower() == 'app':
        pass
    elif RunConfig.RUN.lower() == 'api':
        cells.insert(3, html.th('响应状态码'))
        cells.insert(4, html.th('响应内容'))
    cells.pop()


# 设置用例描述表格
def pytest_html_results_table_row(report, cells):
    cells.insert(2, html.td(report.description))
    if RunConfig.RUN == None or RunConfig.RUN.lower() == 'ui' or RunConfig.RUN.lower() == 'app':
        pass
    elif RunConfig.RUN.lower() == 'api':
        cells.insert(3, html.td(report.code))
        cells.insert(4, html.td(report.body))
    cells.pop()


def pytest_configure(config):
    try:
        config._metadata.pop("JAVA_HOME")
    except Exception:
        pass
    try:
        config._metadata.pop("Plugins")
    except Exception:
        pass
    config._metadata['开始时间'] = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())
    config._metadata['测试人'] = RunConfig.tester
    if RunConfig.RUN == None or RunConfig.RUN.lower() == 'ui':
        # 用于去掉接口测试中的Base URL
        config.run_type = 'ui'
    elif RunConfig.RUN.lower() == 'api':
        # 用于去掉Base URL
        config.run_type = 'api'
        config._metadata['Platform'] = f'Requests-{requests.__version__}'
    else:
        config.run_type = 'app'
        if not RunConfig.app_name:
            config.app_name = RunConfig.desired_caps["deviceName"]
        config.app_name = RunConfig.app_name
        config._metadata['Platform'] = f'platform: {RunConfig.desired_caps["platformName"]}-' \
                                       f'{RunConfig.desired_caps["platformVersion"]}-' \
                                       f'{config.app_name}; Appium: {appium.version.version}'
    if str(RunConfig.ISTEST) == '0' or str(RunConfig.ISTEST).lower() == 'false':
        RunConfig.ISTEST = False
        config.istest = bool(RunConfig.ISTEST)
    else:
        config.istest = True


@pytest.mark.hookwrapper
def pytest_runtest_makereport(item):
    """
    用于向测试用例中添加用例的开始时间、内部注释，和失败截图等.
    :param item:
    """
    if RunConfig.RUN == None or RunConfig.RUN.lower() == 'ui' or RunConfig.RUN.lower() == 'app':
        pytest_html = item.config.pluginmanager.getplugin('html')
        outcome = yield
        report = outcome.get_result()
        report.description = description_html(item.function.__doc__)
        extra = getattr(report, 'extra', [])
        if report.when == 'call' or report.when == "setup":
            xfail = hasattr(report, 'wasxfail')
            if (report.skipped and xfail) or (report.failed and not xfail):
                img_base64 = capture_screenshots()
                html = """<div><img src="data:image/png;base64,%s" alt="screenshot" style="width:304px;height:228px;" 
                            onclick="const img = new Image()
                                    img.src = 'data:image/png;base64,%s'
                                    const newWin = window.open('', '_blank')
                                    newWin.document.write(img.outerHTML)
                                    newWin.document.close()" 
                            align="right"/></div>""" % (img_base64, img_base64)
                extra.append(pytest_html.extras.html(html))
            report.extra = extra
    elif RunConfig.RUN.lower() == 'api':
        # pytest_html = item.config.pluginmanager.getplugin('html')
        outcome = yield
        report = outcome.get_result()
        report.description = description_html(item.function.__doc__)
        if RunConfig.API_RESULT_CODE:
            report.code = RunConfig.API_RESULT_CODE
            report.body = RunConfig.API_RESULT
        else:
            report.code = '无'
            report.body = '无'


def description_html(desc):
    """
    将用例中的描述转成HTML对象
    :param desc: 描述
    :return:
    """
    if desc is None:
        return "没有描述信息！"
    desc_ = ""
    for i in range(len(desc)):
        if i == 0:
            pass
        elif desc[i] == '\n':
            desc_ = desc_ + ";"
        else:
            desc_ = desc_ + desc[i]

    desc_lines = desc_.split(";")
    desc_html = html.html(
        html.head(
            html.meta(name="Content-Type", value="text/html; charset=latin1")),
        html.body(
            [html.p(line) for line in desc_lines]))
    return desc_html


def capture_screenshots():
    """
    配置用例失败截图路径
    :param case_name: 用例名
    :return:
    """
    if RunConfig.RUN is None or RunConfig.RUN.lower() == 'ui':
        global driver
        # file_name = case_name.split("/")[-1]
        if RunConfig.NEW_REPORT is None:
            raise NameError('没有初始化测试报告目录')
        else:
            # image_dir = os.path.join(RunConfig.NEW_REPORT, "image", file_name)
            base64_img = driver.get_screenshot_as_base64()
            return base64_img
            # RunConfig.driver.save_screenshot(image_dir)
    else:
        global app_driver
        filename = 'error' + str(time.time()) + '.png'
        RunConfig.app_driver.get_screenshot_as_file(filename)
        import base64
        base64_img = base64.b64encode(open(filename, 'rb').read())
        os.remove(filename)
        return base64_img


# 用例 call 执行时调用
def pytest_runtest_call(item):
    logger.info(f'开始执行用例-{item.function.__name__}')


# 启动浏览器
@pytest.fixture(scope='session', autouse=True)
def browser(base_url):
    """
    全局定义浏览器驱动
    :return:
    """
    if RunConfig.RUN == None or RunConfig.RUN.lower() == 'ui':
        global driver
        if RunConfig.driver_type == "chrome":
            # 本地chrome浏览器
            # driver = webdriver.Chrome()
            driver = webdriver.Chrome(service=ChromeService(ChromeDriverManager().install()))
            driver.maximize_window()

        elif RunConfig.driver_type == "firefox":
            # 本地firefox浏览器
            driver = webdriver.Firefox()
            driver.maximize_window()

        elif RunConfig.driver_type == "chrome-headless":
            # chrome headless模式
            chrome_options = webdriver.ChromeOptions()
            chrome_options.add_argument("--headless")
            chrome_options.add_argument('--disable-gpu')
            chrome_options.add_argument('--no-sandbox')
            chrome_options.add_argument('--disable-dev-shm-usage')
            chrome_options.add_experimental_option('excludeSwitches', ['enable-logging'])
            # chrome_options.add_argument("--window-size=1920x1080")\
            try:
                driver = webdriver.Chrome(
                    options=chrome_options,
                    service=ChromeService(ChromeDriverManager().install()))
            except Exception as e:
                logger.error(f'浏览器错误{e}')

        elif RunConfig.driver_type == "firefox-headless":
            # firefox headless模式
            firefox_options = webdriver.FirefoxOptions()
            firefox_options.add_argument("--headless")
            firefox_options.add_argument('--disable-gpu')
            firefox_options.add_argument('--no-sandbox')
            firefox_options.add_argument('--disable-dev-shm-usage')
            driver = webdriver.Firefox(
                # firefox_options=firefox_options,
                options=firefox_options,
                service=FirefoxService(GeckoDriverManager().install()))

        elif RunConfig.driver_type == "grid":
            # 通过远程节点运行
            platform = getattr(RunConfig, 'remote_platform')
            ip = getattr(RunConfig, 'remote_ip')
            port = getattr(RunConfig, 'remote_port')
            remote_type = getattr(RunConfig, 'remote_type')
            if not platform:
                platform = 'windows'
            if not ip:
                ip = '127.0.0.1'
            if not port:
                port = '4444'
            # 配置浏览器
            if not remote_type:
                remote_type = 'chrome'
            if remote_type.lower() == 'chrome':
                options = webdriver.ChromeOptions()
            elif remote_type.lower() == 'firefox':
                options = webdriver.FirefoxOptions()
            elif remote_type.lower() == 'edge':
                options = webdriver.EdgeOptions()
            elif remote_type.lower() == 'ie':
                options = webdriver.IeOptions()
            elif remote_type.lower() == 'safari':
                options = SafariOptions()
            else:
                raise ValueError('远程浏览器类型错误，请重新配置')
            if platform.lower() == 'linux':
                options.add_argument("--headless")
                options.add_argument('--disable-gpu')
                options.add_argument('--no-sandbox')
                options.add_argument('--disable-dev-shm-usage')
            driver = Remote(command_executor=f'http://{ip}:{port}/wd/hub', options=options)
            driver.maximize_window()
        else:
            raise NameError("driver驱动类型定义错误！")

        driver.implicitly_wait(RunConfig.timeout)
        RunConfig.driver = driver
        driver.get(base_url)
        return driver


# 初始化Appium driver
@pytest.fixture(scope='session', autouse=True)
def app_driver():
    """
    定义全局的app driver
    return: app_driver
    """
    if RunConfig.RUN is not None and RunConfig.RUN.lower() == 'app':
        # if ('Windows' == platform.system()):
        #     if get_appium():
        #         global app_driver
        #         app_driver = app_webdriver.Remote(command_executor='http://localhost:4723/wd/hub',
        #                                           desired_capabilities=RunConfig.desired_caps)
        #         logger.info(f'初始化app_driver-{app_driver}')
        #         RunConfig.app_driver = app_driver
        #         return app_driver
        #     else:
        #         logger.error('没有检测到启动Appium服务，请启动Appium!')
        pass


# def get_appium():
#     for proc in psutil.process_iter():
#         try:
#             info = proc.as_dict(attrs=['pid', 'name'])
#         except psutil.NoSuchProcess:
#             pass
#         else:
#             if "Appium.exe" in info["name"]:
#                 return True

# 有些问题，去掉这个函数
# @pytest.fixture(scope="session")
# def get_token(tmp_path_factory, worker_id, init_data):
#     if RunConfig.RUN is not None and RunConfig.RUN.lower() == 'api':
#         if not RunConfig.is_remote:
#             root_tmp_dir = tmp_path_factory.getbasetemp().parent
#             fn = root_tmp_dir / "token.json"
#             with FileLock(str(fn) + ".lock"):
#                 if fn.is_file():
#                     data = json.loads(fn.read_text())
#                 else:
#                     data = '登录接口返回的token。'
#                     fn.write_text(json.dumps(data))
#         else:
#             if worker_id == "master":
#                 data = '登录接口返回的token。'
#                 return data
#             root_tmp_dir = tmp_path_factory.getbasetemp().parent
#             fn = root_tmp_dir / "token.json"
#             with FileLock(str(fn) + ".lock"):
#                 if fn.is_file():
#                     data = json.loads(fn.read_text())
#                 else:
#                     data = '登录接口返回的token。'
#         # 正常时返回 data, 本人用不到，为了不影响其他测试，我改为None,有需要请自行修改。
#         # return data
#         return None


# 初始化全局session
@pytest.fixture(scope='session', autouse=True)
def session():
    if RunConfig.RUN is not None and RunConfig.RUN.lower() == 'api':
        global session
        session = requests.session()
        logger.info(f'获取全局session：{session}')
        return session


# 初始化全局数据库链接
@pytest.fixture(scope='session', autouse=True)
def mysql():
    if getattr(RunConfig, 'MySQL', None):
        global mysql
        mysql = MySql()
        logger.info(f'初始化mysql：{mysql}')
        return mysql


@pytest.fixture(scope='session', autouse=True)
def base_ip(request):
    """Return a base IP"""
    config = request.config
    if RunConfig.RUN is not None and RunConfig.RUN.lower() == 'api':
        if config.istest:
            base_ip = config.getini("test_base_ip")
        else:
            base_ip = config.getini("base_ip")
        if base_ip is not None:
            return base_ip


def pytest_addoption(parser):
    parser.addini("test_base_ip", help="test base ip for the session under test.")
    parser.addini("base_ip", help="test base ip for the session under test.")
    parser.addini("test_base_url", help="test base url for the application under test.")


# 关闭浏览器 或者全局session
@pytest.fixture(scope="session", autouse=True)
def close():
    if RunConfig.RUN is None or RunConfig.RUN.lower() == 'ui':
        yield driver
        driver.quit()
    elif RunConfig.RUN.lower() == 'api':
        yield session
        session.close()
    else:
        yield app_driver
        app_driver.terminate_app(RunConfig.desired_caps['appPackage'])
    if getattr(RunConfig, 'MySQL', None):
        mysql.close()
        logger.info('mysql close!')
    logger.info("test end!")


if __name__ == "__main__":
    # 启动selenium-server
    pass
